#include "VRPXml.h"

#include <QFile>
#include <QList>

#include <QDebug>

#include "VRPObjects.h"

VRPXml::VRPXml(QObject *parent) :
    QObject(parent)
{
}

VRPXml::~VRPXml()
{
}

int VRPXml::loadGraph(VRPRouteGraphTraits::Graph &graph, QList<VehiclePtr> &vehicleList, const QString &fileName)
{
    QFile file(fileName);
    if(!file.open(QFile::ReadOnly | QFile::Text))
        return XML_ERROR_FILE;

    /// Clear graph before all.
    graph.clear();

    /// Store graph vertex name and descr in map.
    /// We do it for manage edges.
    QMap<QString, VRPRouteGraphTraits::Vertex> vertexMap;

    /// Init xml reader.
    QXmlStreamReader xmlReader(&file);

    try
    {
        /// We'll parse the XML until we reach end of it.
        while(!xmlReader.atEnd() && !xmlReader.hasError())
        {
            QXmlStreamReader::TokenType token = xmlReader.readNext();
            if(token == QXmlStreamReader::StartDocument)
                continue;

            //qDebug()<<"Token name "<<xmlReader.name();

            if(token == QXmlStreamReader::StartElement)
            {
                if(xmlReader.name() == "VRPRouteGraph" ||
                   xmlReader.name() == "Vertices"      ||
                   xmlReader.name() == "Edges"         ||
                   xmlReader.name() == "Vehicles")
                    continue;

                if(xmlReader.name() == "Vertex")
                    if(!readVertex(graph, xmlReader, vertexMap))
                        throw -1;

                if(xmlReader.name() == "Edge")
                    if(!readEdge(graph, xmlReader, vertexMap))
                        throw -1;
                if(xmlReader.name() == "Vehicle")
                    if(!readVehicle(vehicleList, xmlReader))
                        throw -1;
            }
        }
        if(xmlReader.hasError())
            throw -1;
    }
    catch(int &e)
    {
        qDebug()<<xmlReader.errorString();
        graph.clear();
        vehicleList.clear();
        xmlReader.clear();
        return XML_ERROR;
    }

    xmlReader.clear();
    return XML_SUCCESS;
}

bool VRPXml::readVehicle(QList<VehiclePtr>& vehicleList, QXmlStreamReader& xml)
{
    VehiclePtr vehicle(new Vehicle("Vehicle #" + QString::number(vehicleList.size() + 1)));

    /// Let's check that we're really getting a Vertex.
    if(xml.tokenType() != QXmlStreamReader::StartElement && xml.name() == "Vehicle")
        return false;

    /// Next element...
    xml.readNext();

    /// We're going to loop over the things because the order might change.
    /// We'll continue the loop until we hit an EndElement named Vertex.
    while(!(xml.tokenType() == QXmlStreamReader::EndElement && xml.name() == "Vehicle"))
    {
        if(xml.tokenType() == QXmlStreamReader::StartElement)
        {
            if(xml.name() == "Name")
            {
                QString vn = getTextElement(xml);
                if(vn.isEmpty())
                    return false;
                vehicle->setName(vn);
            }
            else if(xml.name() == "Cost")
            {
                QString vc = getTextElement(xml);
                if(vc.isEmpty())
                    return false;
                vehicle->setCost(vc.toInt());
            }
            else if(xml.name() == "Cost")
            {
                QString vcap = getTextElement(xml);
                if(vcap.isEmpty())
                    return false;
                vehicle->setCapacity(vcap.toInt());
            }
        }
        xml.readNext();
    }

    //qDebug()<<"vehicle: "<<vehicle->name()<<" "<<vehicle->cost()<<" "<<vehicle->capacity();
    vehicleList.push_back(vehicle);

    return true;
}

bool VRPXml::readVertex(VRPRouteGraphTraits::Graph &graph,
                        QXmlStreamReader &xml,
                        QMap<QString, VRPRouteGraphTraits::Vertex> &vertexMap)
{
    VRPRouteGraphTraits::VertexNameMap nameMap = VRPRouteGraphTraits::GetVertexNameMap(graph);
    VRPRouteGraphTraits::VertexBackhaulMap bhMap = VRPRouteGraphTraits::GetVertexBackhaulMap(graph);
    VRPRouteGraphTraits::VertexDemandMap demandMap = VRPRouteGraphTraits::GetVertexDemandMap(graph);
    VRPRouteGraphTraits::VertexCoordinateMap coordMap = VRPRouteGraphTraits::GetVertexCoordinateMap(graph);

    std::string vertexName = "";
    std::size_t vertexDemand = -1;
    bool vertexBh = false;
    CirclePointPtr vertexCoord;
    bool isDepo = false;

    /// Let's check that we're really getting a Vertex.
    if(xml.tokenType() != QXmlStreamReader::StartElement && xml.name() == "Vertex")
        return false;

    /// Next element...
    xml.readNext();

    /// We're going to loop over the things because the order might change.
    /// We'll continue the loop until we hit an EndElement named Vertex.
    while(!(xml.tokenType() == QXmlStreamReader::EndElement && xml.name() == "Vertex"))
    {
        if(xml.tokenType() == QXmlStreamReader::StartElement)
        {
            if(xml.name() == "Name")
            {
                vertexName = getTextElement(xml).toStdString();
                if(vertexName.empty())
                    return false;
            }
            else if(xml.name() == "Type")
            {
                QString type = getTextElement(xml);
                if(type.isEmpty())
                    return false;
                if(type == "Depo")
                    isDepo = true;
            }
            else if(xml.name() == "CustomerType")
            {
                QString customerType = getTextElement(xml);
                if(customerType.isEmpty())
                    return false;
                if(customerType == "Backhaul")
                    vertexBh = true;
            }
            else if(xml.name() == "Demand")
            {
                bool isCastOk = false;
                QString demand = getTextElement(xml);
                if(demand.isEmpty())
                    return false;
                vertexDemand = demand.toInt(&isCastOk);
                if(!isCastOk)
                    return false;
            }
            else if(xml.name() == "Coordinate")
            {
                vertexCoord = getCirclePoint(xml, QString::fromStdString(vertexName), isDepo);
                if(!vertexCoord)
                    return false;
            }
        }
        xml.readNext();
    }

    VRPRouteGraphTraits::Vertex vertex;
    if(isDepo)
    {
        if(vertexName.empty() || !vertexCoord)
            return false;
        vertex = boost::add_vertex(graph);
        nameMap[vertex] = vertexName;
        coordMap[vertex] = vertexCoord;
    }
    else
    {
        if(vertexName.empty() || !vertexCoord || (vertexDemand == -1))
            return false;
        vertex = boost::add_vertex(graph);
        nameMap[vertex] = vertexName;
        coordMap[vertex] = vertexCoord;
        bhMap[vertex] = vertexBh;
        demandMap[vertex] = vertexDemand;
    }

    vertexMap[QString::fromStdString(vertexName)] = vertex;
    return true;
}

bool VRPXml::readEdge(VRPRouteGraphTraits::Graph &graph,
                      QXmlStreamReader &xml,
                      QMap<QString, VRPRouteGraphTraits::Vertex> &vertexMap)
{
    VRPRouteGraphTraits::EdgeRouteMap routeMap = VRPRouteGraphTraits::GetEdgeRouteMap(graph);
    VRPRouteGraphTraits::EdgeWeightMap weightMap = VRPRouteGraphTraits::GetEdgeWeightMap(graph);

    std::size_t edgeWeight = -1;
    LineStringPtr edgeRoute;
    QString targetVertexName;
    QString sourceVertexName;

    /// Let's check that we're really getting an Edge.
    if(xml.tokenType() != QXmlStreamReader::StartElement && xml.name() == "Edge")
        return false;

    /// Next element...
    xml.readNext();

    while(!(xml.tokenType() == QXmlStreamReader::EndElement && xml.name() == "Edge"))
    {
        if(xml.tokenType() == QXmlStreamReader::StartElement)
        {
            if(xml.name() == "TargetVertexName")
            {
                targetVertexName = getTextElement(xml);
                if(targetVertexName.isEmpty())
                    return false;
            }
            else if(xml.name() == "SourceVertexName")
            {
                sourceVertexName = getTextElement(xml);
                if(sourceVertexName.isEmpty())
                    return false;
            }
            else if(xml.name() == "Weight")
            {
                bool isCastOk = false;
                QString wStr = getTextElement(xml);
                if(wStr.isEmpty())
                    return false;
                edgeWeight = wStr.toInt(&isCastOk);
                if(!isCastOk)
                    return false;
            }
            else if(xml.name() == "LineString")
            {
                edgeRoute = getLineString(xml, "From " + sourceVertexName + " To " + targetVertexName);
            }
        }
        xml.readNext();
    }

    if(sourceVertexName.isEmpty() || targetVertexName.isEmpty() || (edgeWeight == -1) || !edgeRoute)
        return false;

    VRPRouteGraphTraits::Edge edge;
    bool inseted = false;
    boost::tie(edge, inseted) = boost::add_edge(vertexMap[sourceVertexName], vertexMap[targetVertexName], graph);
    if(!inseted)
        return false;

    weightMap[edge] = edgeWeight;
    routeMap[edge] = edgeRoute;

    return true;
}

QString VRPXml::getTextElement(QXmlStreamReader &xml)
{
    /// We need a start element, like <foo>
    if(xml.tokenType() != QXmlStreamReader::StartElement)
        return QString();

    xml.readNext();
    if(xml.tokenType() != QXmlStreamReader::Characters)
        return QString();

    return xml.text().toString();
}

CirclePointPtr VRPXml::getCirclePoint(QXmlStreamReader &xml, const QString &pointName, bool isDepo)
{
    /// Let's check that we're really getting a CirclePointPtr().
    if(xml.tokenType() != QXmlStreamReader::StartElement && xml.name() == "Coordinate")
        return CirclePointPtr();

    bool isCastOk = false;
    double longitude = 0.0;
    double latitude = 0.0;

    QStringRef tmp = xml.attributes().value("Longitude");
    if(tmp.isEmpty())
        return CirclePointPtr();
    longitude = tmp.toString().toDouble(&isCastOk);

    tmp = xml.attributes().value("Latitude");
    if(tmp.isEmpty())
        return CirclePointPtr();
    latitude = tmp.toString().toDouble(&isCastOk);

    if(!isCastOk)
        return CirclePointPtr();

    VRPPixmapsFactory* pf = VRPPixmapsFactory::instance();
    QPixmap* cPixmap = isDepo?pf->createdDepoPixmap():pf->createCustomerPixmap();

    return CirclePointPtr(new qmapcontrol::Point(longitude, latitude, cPixmap, pointName));
}

LineStringPtr VRPXml::getLineString(QXmlStreamReader &xml, QString name)
{
    QString lineStringText;

    /// Let's check that we're really getting an LineString.
    if(xml.tokenType() != QXmlStreamReader::StartElement && xml.name() == "LineString")
        return LineStringPtr();

    /// Next element...
    xml.readNext();

    while(!(xml.tokenType() == QXmlStreamReader::EndElement && xml.name() == "LineString"))
    {
        if(xml.tokenType() == QXmlStreamReader::StartElement)
        {
            if(xml.name() == "coordinates")
            {
                lineStringText = getTextElement(xml);
                if(lineStringText.isEmpty())
                    return LineStringPtr();
            }
        }
        xml.readNext();
    }

    return parseStringWithPoints(lineStringText, name);
}

LineStringPtr VRPXml::parseStringWithPoints(const QString &pointsStr, QString name)
{
    QStringList innerStrList;
    QStringList strList;
    strList = pointsStr.split(" ");

    QList<qmapcontrol::Point*> points;

    QStringListIterator it(strList);
    while(it.hasNext())
    {
        innerStrList = it.next().split(',');
        if(innerStrList.size() != 2)
            continue;
        qmapcontrol::Point* tmpPoint = new qmapcontrol::Point(innerStrList.at(0).toDouble(), innerStrList.at(1).toDouble());
        points.append(tmpPoint);
    }

    LineStringPtr retRoute(new qmapcontrol::LineString(points, name, VRPPixmapsFactory::instance()->routePen()));

    return retRoute;
}

int VRPXml::saveGraph(VRPRouteGraphTraits::Graph &graph, const QList<VehiclePtr> &vehicleList, const QString &fileName)
{
    QFile file(fileName);
    if(!file.open(QFile::WriteOnly | QFile::Text))
        return XML_ERROR_FILE;

    VehiclePtr vehicle;
    QXmlStreamWriter xmlWriter(&file);
    xmlWriter.setAutoFormatting(true);
    xmlWriter.writeStartDocument();
    xmlWriter.writeStartElement("VRPRouteGraph");
    saveVertices(graph, xmlWriter);
    saveEdges(graph, xmlWriter);
    xmlWriter.writeStartElement("Vehicles");
    foreach(vehicle, vehicleList)
    {
        xmlWriter.writeStartElement("Vehicle");
        xmlWriter.writeTextElement("Name", vehicle->name());
        xmlWriter.writeTextElement("Cost", QString::number(vehicle->cost()));
        xmlWriter.writeTextElement("Capacity", QString::number(vehicle->capacity()));
        xmlWriter.writeEndElement();//Vehicle
    }
    xmlWriter.writeEndElement();//Vehicles
    xmlWriter.writeEndElement();//VRPRouteGraph
    xmlWriter.writeEndDocument();
    file.close();
    if(file.error())
        return XML_ERROR_FILE;

    return XML_SUCCESS;
}

void VRPXml::saveVertices(VRPRouteGraphTraits::Graph &graph, QXmlStreamWriter &xmlWriter)
{
    VRPRouteGraphTraits::VertexNameMap nameMap = VRPRouteGraphTraits::GetVertexNameMap(graph);
    VRPRouteGraphTraits::VertexBackhaulMap bhMap = VRPRouteGraphTraits::GetVertexBackhaulMap(graph);
    VRPRouteGraphTraits::VertexDemandMap demandMap = VRPRouteGraphTraits::GetVertexDemandMap(graph);
    VRPRouteGraphTraits::VertexCoordinateMap coordMap = VRPRouteGraphTraits::GetVertexCoordinateMap(graph);

    xmlWriter.writeStartElement("Vertices");

    bool isDepo = false;
    std::pair<VRPRouteGraphTraits::VertexIterator, VRPRouteGraphTraits::VertexIterator> beginPair;
    beginPair = boost::vertices(graph);
    VRPRouteGraphTraits::VertexIterator beginIt = beginPair.first;
    std::pair<VRPRouteGraphTraits::VertexIterator, VRPRouteGraphTraits::VertexIterator> vp;

    for(vp = boost::vertices(graph); vp.first != vp.second; vp.first++)
    {
        xmlWriter.writeStartElement("Vertex");

        xmlWriter.writeTextElement("Name", QString::fromStdString(nameMap[*vp.first]));
        isDepo = (vp.first == beginIt);
        if(isDepo)
            xmlWriter.writeTextElement("Type", "Depo");
        else
        {
            xmlWriter.writeTextElement("Type", "Customer");
            xmlWriter.writeTextElement("CustomerType", bhMap[*vp.first]?"Backhaul":"Linehaul");
            xmlWriter.writeTextElement("Demand", QString::number(demandMap[*vp.first]));
        }
        CirclePointPtr point = coordMap[*vp.first];
        xmlWriter.writeStartElement("Coordinate");
        xmlWriter.writeAttribute("Longitude", QString::number(point->longitude()));
        xmlWriter.writeAttribute("Latitude", QString::number(point->latitude()));
        xmlWriter.writeEndElement();//Coordinate

        xmlWriter.writeEndElement();//Vertex
    }

    xmlWriter.writeEndElement();//Vertices
}

void VRPXml::saveEdges(VRPRouteGraphTraits::Graph &graph, QXmlStreamWriter &xmlWriter)
{
    VRPRouteGraphTraits::EdgeRouteMap routeMap = VRPRouteGraphTraits::GetEdgeRouteMap(graph);
    VRPRouteGraphTraits::EdgeWeightMap weightMap = VRPRouteGraphTraits::GetEdgeWeightMap(graph);
    VRPRouteGraphTraits::VertexNameMap nameMap = VRPRouteGraphTraits::GetVertexNameMap(graph);

    xmlWriter.writeStartElement("Edges");

    std::pair<VRPRouteGraphTraits::EdgeIterator, VRPRouteGraphTraits::EdgeIterator> ep;
    for(ep = boost::edges(graph); ep.first != ep.second; ep.first++)
    {
        QString targetVertexName = QString::fromStdString(nameMap[boost::target(*ep.first, graph)]);
        QString sourceVertexName = QString::fromStdString(nameMap[boost::source(*ep.first, graph)]);

        xmlWriter.writeStartElement("Edge");

        xmlWriter.writeTextElement("TargetVertexName", targetVertexName);
        xmlWriter.writeTextElement("SourceVertexName", sourceVertexName);
        xmlWriter.writeTextElement("Weight", QString::number(weightMap[*ep.first]));
        writeLineString(routeMap[*ep.first], xmlWriter);

        xmlWriter.writeEndElement();//Edge
    }

    xmlWriter.writeEndElement();//Edges

}

void VRPXml::writeLineString(LineStringPtr lineString, QXmlStreamWriter &xmlWriter)
{
    QString lsStr = "";
    QList<qmapcontrol::Point*> lsPoints = lineString->points();

    qmapcontrol::Point* point;
    foreach(point, lsPoints)
    {
        lsStr += QString::number(point->longitude());
        lsStr += ",";
        lsStr += QString::number(point->latitude());
        lsStr += " ";
    }

    xmlWriter.writeStartElement("LineString");
    xmlWriter.writeTextElement("coordinates", lsStr);
    xmlWriter.writeEndElement();
}
